import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.LinkedList;


public class PitchPressureCorrectorFilter extends InstrumentationFilterFramework{
	/*************************************************************
	 *	This filter corrects "wild" values in the data 
	 *	Data that is need for the checking process is passed on immediately
	 *************************************************************/

	//output files
	private static File wildFile;

	//pressure constants
	private static double MAX_PRESSURE;
	private static double MAX_PITCH;

	//lists to hold data before written to file
	final private LinkedList<Long> times = new LinkedList<Long>();
	final private LinkedList<Double> psi = new LinkedList<Double>(); //pressure
	final private LinkedList<Double> pit = new LinkedList<Double>(); //pitch
	final private LinkedList<WildCode> wildCode = new LinkedList<WildCode>();//provides essentially error codes 

	//Formatting section
	//CHANGE: Originally, the date format was "yyyy MM dd::hh:mm:ss:SSS"
	final private Calendar TimeStamp = Calendar.getInstance();
	final private SimpleDateFormat TimeStampFormat = new SimpleDateFormat("yyyy:DD:HH:mm:ss");
	final private DecimalFormat psiFormat = new DecimalFormat("00.00000");
	//end of formatting

	PitchPressureCorrectorFilter(String fileName, double maxPitch, double maxPressure){
		wildFile = new File(fileName);
		MAX_PRESSURE = maxPressure;
		MAX_PITCH = maxPitch;
	}

	public void run()
	{
		boolean firstValidPsi=false;
		boolean firstValidPit=false;
		boolean checkVals = false;

		Double lastValidPsi = new Double(Double.NaN);
		Double lastValidPit = new Double(Double.NaN);
		int smallestIndex = 0;						//used to keep track of the psi and time 
		//indices in case the data from the stream arrives
		//out of sync.

		int MeasurementLength = 8;		// This is the length of all measurements (including time) in bytes
		int IdLength = 4;				// This is the length of IDs in the byte stream

		byte databyte = 0;				// This is the data byte read from the stream
		int bytesread = 0;				// This is the number of bytes read from the stream

		long measurement;				// This is the word used to store all measurements - conversions are illustrated.
		int id;							// This is the measurement id
		int i;							// This is a loop counter

		/*************************************************************
		 *	First we announce to the world that we are alive...
		 **************************************************************/

		System.out.print( "\n" + this.getName() + "::PPCorrector Reading ");


		try{
			wildFile.delete();
			wildFile.createNewFile();
			FileWriter wildWriter = new FileWriter(wildFile);
			wildWriter.write("Time: \t\t\tPressure (PSI):\tPitch (degrees):\n");

			while (true)
			{
				try
				{
					/*************************************************************
					 *	Read id
					 **************************************************************/

					id = 0;

					for (i=0; i<IdLength; i++ )
					{
						databyte = ReadFilterInputPort();	// This is where we read the byte from the stream...

						id = id | (databyte & 0xFF);		// We append the byte on to ID...

						if (i != IdLength-1)				// If this is not the last byte, then slide the
						{									// previously appended byte to the left by one byte
							id = id << 8;					// to make room for the next byte we append to the ID

						} // if

						bytesread++;						// Increment the byte count

					} // for

					/*************************************************************
					 *	Read measurements
					 **************************************************************/

					measurement = 0;

					for (i=0; i<MeasurementLength; i++ )
					{
						databyte = ReadFilterInputPort();
						measurement = measurement | (databyte & 0xFF);	// We append the byte on to measurement...

						if (i != MeasurementLength-1)					// If this is not the last byte, then slide the
						{												// previously appended byte to the left by one byte
							measurement = measurement << 8;				// to make room for the next byte we append to the
							// measurement
						} // if

						bytesread++;									// Increment the byte count

					} // if

					/*************************************************************
					 *	Classify the measurement
					 **************************************************************/

					if ( id == Measurement.TIME.getID() )
					{
						
						//adds the time measurement
						times.add(new Long(measurement));

						//sends the time measurement immediately
						writeMeasurementToFilterOutputPort(Measurement.TIME, measurement);

						//if time is the "lagging" value,
						//this check all three values
						if(
								pit.size()>=times.size() &&
								psi.size()>=times.size() 
								){
							checkVals = true;
						}						
					} // if

					else if ( id == Measurement.VELOCITY.getID())
					{	
						//convert to double
						double vel = Double.longBitsToDouble(measurement);

						//sends the altitude measurement immediately
						writeMeasurementToFilterOutputPort(Measurement.VELOCITY, new Double(vel));

					} // if

					else if ( id == Measurement.ALTITUDE.getID())
					{	
						//convert to double
						double altMeters = Double.longBitsToDouble(measurement);

						//sends the altitude measurement immediately
						writeMeasurementToFilterOutputPort(Measurement.ALTITUDE, new Double(altMeters));

					} // if

					else if ( id == Measurement.PRESSURE.getID() )
					{	
						
						//convert to double
						double pressure = Double.longBitsToDouble(measurement);

						//adds the pressure measurement
						psi.add(new Double(pressure));
						
						//if pressure is the "lagging" value,
						//this check all three values
						if(
								times.size()>=psi.size() &&
								pit.size()>=psi.size() 
								){
							checkVals = true;
						}

					} // if

					else if ( id == Measurement.TEMPERATURE.getID() )
					{
						//convert to double
						double celsius = (Double.longBitsToDouble(measurement));

						//sends the temperature measurement immediately
						writeMeasurementToFilterOutputPort(Measurement.TEMPERATURE, new Double(celsius));

					} // if
					else if ( id == Measurement.PITCH.getID() )
					{
						
						//convert to double
						double pitch = (Double.longBitsToDouble(measurement));

						pit.add(pitch);
						
						//if pressure is the "lagging" value,
						//this check all three values
						if(
								times.size()>=pit.size() &&
								psi.size()>=pit.size() 
								){
							checkVals = true;
						}

					} // if


					/*************************************************************
					 *	Sets last common index to prevent checked value information from being out-of-sync
					 **************************************************************/

					smallestIndex = (times.size()-1);

					if (smallestIndex+1 > psi.size()){
						smallestIndex = (psi.size()-1);
					}

					if (smallestIndex+1 > pit.size()){
						smallestIndex = (pit.size()-1);
					}

					/*************************************************************
					 *	Should the checked information be examined?
					 **************************************************************/

					if(		checkVals &&
							smallestIndex > -1
					){

						Double examinedPsi = psi.get(smallestIndex); //the psi being examined
						Double examinedPit = pit.get(smallestIndex); //pitch being examined
						
						if(examinedPit ==10.0)
							System.out.println("Debugging pitch");

						/*************************************************************
						 *	Check for new wild points
						 **************************************************************/
						if (isValidPsi(examinedPsi)){
							wildCode.add(WildCode.NO_WILD);
						}
						else{
							wildCode.add(WildCode.PSI_WILD);
						}
						if(isValidPit(times.get(smallestIndex), examinedPit)){
							//do nothing else
						}
						else{
							if(wildCode.getLast()==WildCode.NO_WILD){
								wildCode.removeLast();
								wildCode.add(WildCode.PIT_WILD);
							}
							else{
								wildCode.removeLast();
								wildCode.add(WildCode.PSI_AND_PIT_WILD);
							}
						}

						/*************************************************************
						 *	Handling wild point
						 **************************************************************/
						WildCode lastWildCode =wildCode.getLast();
						if(
								lastWildCode==WildCode.PIT_WILD ||
								lastWildCode==WildCode.PSI_WILD ||
								lastWildCode==WildCode.PSI_AND_PIT_WILD){

							writeWildPoint(wildWriter, times.getLast(), examinedPsi, examinedPit);
						}

						/*************************************************************
						 *	Handling a partially valid values
						 **************************************************************/
						 
						if(
								lastWildCode == WildCode.PIT_WILD ||
								lastWildCode == WildCode.NO_WILD
						){
							//this means that all prior pressures can now be calculated, if necessary

							/*************************************************************
							 *	handles first valid Psi
							 **************************************************************/
							if (firstValidPsi == false){
								firstValidPsi = true;

								//replaces the psi value if appropriate, calculates the new lastValidPsi,
								//and changes the WildCode if appropriate
								for (i = 0; i<smallestIndex;i++){

									if (wildCode.get(i)==WildCode.PSI_WILD){
										//replaces psi
										psi.remove(i);
										psi.add(i, examinedPsi);

										//replaces wildCode
										wildCode.remove(i);
										wildCode.add(i, WildCode.NO_WILD);
									}
									else if(wildCode.get(i)==WildCode.PSI_AND_PIT_WILD){
										//replaces psi
										psi.remove(i);
										psi.add(i, examinedPsi);

										//replaces wildCode
										wildCode.remove(i);
										wildCode.add(i, WildCode.PIT_WILD);
									}
									else {
										//do nothing
									}

								}

								System.out.println("First Valid Pressure: "+examinedPsi);

								//records the value to be used for computing later
								lastValidPsi = examinedPsi;

							}
							/*************************************************************
							 *	handles not the first valid Psi
							 **************************************************************/
							else{								
								//replaces the psi value if appropriate, calculates the new lastValidPsi,
								//and changes the WildCode if appropriate
								for (i = 0; i<smallestIndex;i++){

									if (wildCode.get(i)==WildCode.PSI_WILD){
										System.out.println("Computing pressure: ");
										System.out.println("Last valid pressure: "+lastValidPsi);
										System.out.println("New valid pressure: "+examinedPsi);

										//replaces psi
										psi.remove(i);
										lastValidPsi =(lastValidPsi+examinedPsi)/2.0; 
										System.out.println("New computed pressure: "+lastValidPsi+"\n");
										psi.add(i, lastValidPsi);

										//replaces wildCode
										wildCode.remove(i);
										wildCode.add(i, WildCode.NO_WILD);
									}
									else if(wildCode.get(i)==WildCode.PSI_AND_PIT_WILD){
										System.out.println("Computing pressure: ");
										System.out.println("Last valid pressure: "+lastValidPsi);
										System.out.println("New valid pressure: "+examinedPsi + "\n");

										//replaces psi
										psi.remove(i);
										lastValidPsi =(lastValidPsi+examinedPsi)/2.0;
										System.out.println("New computed pressure: "+lastValidPsi+"\n");
										psi.add(i, lastValidPsi);

										//replaces wildCode
										wildCode.remove(i);
										wildCode.add(i, WildCode.PIT_WILD);
									}
									else {
										//do nothing
									}

								}
							}
						}//end of handling Psi
						if (
								lastWildCode == WildCode.PSI_WILD ||
								lastWildCode == WildCode.NO_WILD
						){
							//this means that all prior pitches can now be calculated, if necessary

							/*************************************************************
							 *	handles first valid Pit
							 **************************************************************/
							if (firstValidPit == false){
								firstValidPit = true;

								//replaces the psi value if appropriate, calculates the new lastValidPsi,
								//and changes the WildCode if appropriate
								for (i = 0; i<smallestIndex;i++){

									if (wildCode.get(i)==WildCode.PIT_WILD){
										//replaces psi
										pit.remove(i);
										pit.add(i, examinedPit);

										//replaces wildCode
										wildCode.remove(i);
										wildCode.add(i, WildCode.NO_WILD);
									}
									else if(wildCode.get(i)==WildCode.PSI_AND_PIT_WILD){
										//replaces pit
										pit.remove(i);
										pit.add(i, examinedPit);

										//replaces wildCode
										wildCode.remove(i);
										wildCode.add(i, WildCode.PIT_WILD);
									}
									else {
										//do nothing
									}

								}

								System.out.println("First Valid Pitch: "+examinedPit);

								//records the value to be used for computing later
								lastValidPit = examinedPit;

							}
							/*************************************************************
							 *	handles not the first valid Pit
							 **************************************************************/
							else{								
								//replaces the pit value if appropriate, calculates the new lastValidPit,
								//and changes the WildCode if appropriate
								for (i = 0; i<smallestIndex;i++){

									if (wildCode.get(i)==WildCode.PIT_WILD){
										System.out.println("Computing pitch: ");
										System.out.println("Last valid pitch: "+lastValidPit);
										System.out.println("New valid pitch: "+examinedPit);

										//replaces psi
										pit.remove(i);
										lastValidPit =(lastValidPit+examinedPit)/2.0; 
										System.out.println("New computed pitch: "+lastValidPit+"\n");
										pit.add(i, lastValidPit);

										//replaces wildCode
										wildCode.remove(i);
										wildCode.add(i, WildCode.NO_WILD);
									}
									else if(wildCode.get(i)==WildCode.PSI_AND_PIT_WILD){
										System.out.println("Computing pitch: ");
										System.out.println("Last valid pitch: "+lastValidPit);
										System.out.println("New valid pitch: "+examinedPit + "\n");

										//replaces psi
										pit.remove(i);
										lastValidPit =(lastValidPit+examinedPit)/2.0;
										System.out.println("New computed pitch: "+lastValidPit+"\n");
										pit.add(i, lastValidPit);

										//replaces wildCode
										wildCode.remove(i);
										wildCode.add(i, WildCode.PSI_WILD);
									}
									else {
										//do nothing
									}

								}
							}
						}//end of handling Pit
						/*************************************************************
						 *	Now that any prior values are calculated, check to see if any of those values can be sent
						 **************************************************************/
						i=0;
						while (i<=smallestIndex){
							if(wildCode.getFirst() == WildCode.NO_WILD){
								lastValidPsi = psi.getFirst();
								lastValidPit = pit.getFirst();
								removeAndSendValues("",lastValidPsi,lastValidPit);
							}
							else{
								break;
							}
							i++;
						}
					}

				} // try

				catch (EndOfStreamException e)
				{
					/*************************************************************
					 *	Handling no valid values at the end of the stream and no valid values cases 
					 **************************************************************/

					while (times.size()>0){
						removeAndSendValues("Sent Last Valid Pressure(s): ", lastValidPsi, lastValidPit);
					}
					ClosePorts();
					System.out.print( "\n" + this.getName() + "::PPCorrector Exiting; bytes read: " + bytesread +"\n");
					break;

				} // catch
				checkVals = false;
			} // while
			/*************************************************************
			 *	Close the writer
			 **************************************************************/
			
			wildWriter.close();
		}catch (IOException ioE){
			ioE.printStackTrace();
		}
		
	} // run

	private boolean isValidPsi(Double psi){
		//if the value of the pressure is greater than 80 or less than 50
		if(
				psi.doubleValue() <= MAX_PRESSURE){
			System.out.println(psi + " is valid");
			return true;
		}
		else{
			System.out.println(psi + " is a wild point!");
			return false;
		}
	}
	private boolean isValidPit(long time, Double pit){
		//if the value of the pressure is greater than 80 or less than 50
		if(
				pit.doubleValue() <= MAX_PITCH){
			System.out.println("Time: "+time+"; Pitch: "+pit + " is valid");
			return true;
		}
		else{
			System.out.println("Time: "+time+"; Pitch: "+pit + " is a wild point!");
			return false;
		}
	}
	private void writeWildPoint(FileWriter wildWriter, Long time, Double pressure, Double pitch) throws IOException{
		//writes the wild pressure point to file
		//this abstracts away the formatting of the writing, while keeping it consistent, in case the spacing needs to be changed
		
		TimeStamp.setTimeInMillis(time);
		wildWriter.write("\n"+TimeStampFormat.format(TimeStamp.getTime())+"\t"+ psiFormat.format(pressure.doubleValue())+"\t\t"+ pitch);
		wildWriter.flush();
	}

	private void removeAndSendValues(String message, Double psiToSend, Double pitToSend){
		System.out.println("Time: "+times.remove()+" removed");
		System.out.println("Pressure: "+psi.remove()+" removed");
		System.out.println("Pitch: "+pit.remove()+" removed");
		wildCode.remove();
		System.out.println(
				message +
				writeMeasurementToFilterOutputPort(Measurement.PRESSURE, new Double(psiToSend))
		);
		System.out.println(
				message +
				writeMeasurementToFilterOutputPort(Measurement.PITCH, new Double(pitToSend))
		);
		System.out.println();
	}

	enum WildCode{
		NO_WILD,
		PSI_WILD,
		PIT_WILD,
		PSI_AND_PIT_WILD,
	}
	/*
	private void printContents(){
		System.out.println("\n\nContents");
		for (int i =0; i<times.size(); i++){
			System.out.println("Times: "+times.get(i)+" "+"Pressure: "+psi.get(i)+" "+"Pitch: "+pit.get(i)+" " +"WildCode: "+wildCode.get(i));
		}
	}
	*/
}

